NodeJS - 理解 内存控制

简介

V8 具有内存限制,所有的 JavaScript 对象都是通过堆来进行分配的,可以通过 process.memoryUsage() 来查看内存信息,其中 heapTotal 和 heapUsed 分别代表了已申请到的对内存和当前使用的量。如果当前代码已申请的堆空闲内存不够分配新的对象,将就申请堆内存,知道堆的大小超过 V8 的内存限制为止

为何采取 V8 内存限制:一次小的垃圾回收需要 50ms 以上,一次非增量式的垃圾回收甚至需要 1s 以上,这是垃圾回收引起的 JS 线程暂停执行的时间,应用的性能和响应时间都会大幅度下降

V8 的垃圾回收机制

采用 node –trace-gc xxx 命令行参数来查看内存垃圾回收日志,使用 –prof 可以得到 V8 执行时的性能分析数据,配合 tick-processor 工具查看统计信息

垃圾回收算法
  • 基于分代式垃圾回收(解决不同的对象生命周期不同)

    --max-old-space-size 指定老生代内存空间的最大值 + --max-new-space-size 指定新生代内存空间的最大值 = V8 堆的整体大小

  • 新生代的回收算法:Scavenge(base on Cheney)

    核心思想:空间一分为二,从 From 空间 复制 到 To 空间,缺点是只能使用堆内存的一般,好处是以空间换取时间(块),适合新生代频繁的内存回收

    对象晋升两个条件:是否经历过 Scavenge 回收,To空间的内存占用是否超过了比例(25%)

  • 老生代的回收算法:Mark-Sweep & Mark-Compact

    核心思想:标记 - 清楚 - 整理内存碎片

  • 回收策略:延迟清理(lazy sweeping)和 增量式整理(incremental compaction)

    为了降低全堆垃圾回收带来的线程停顿问题,采用增量标记来减少由于垃圾回收造成的停顿时间

高效使用内存

在正常的 JavaScript 执行中,无法立即回收的内存有必报和全局变量引用这两种情况,由于 V8 的内存限制,要十分小心此类变量是否无限制的增加,因为他会导致老生代的对象增多

内存指标

  • 使用 process.memoryUsage() 查看 Node 进程的内存占用情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// outofmemory.js

function showMem(){
var mem = process.memoryUsage();
var format = function(bytes){
return (bytes / 1024 / 1024).toFixed(2) + 'MB';
}
console.log(`Process: heapTotal ${format(mem.heapTotal)} heapUsed ${format(mem.heapUsed)} rss ${mem.rss}`);
}

var useMem = function(){
var size = 20 * 1024 * 1024;
var arr = new Array(size);
for(var i = 0; i < size; i++){
arr[i] = 0;
}
return arr;
}

var total = [];
for(var j = 0; j < 15; j++){
showMem();
total.push(useMem());
}
showMem();
  • 使用 os.totalmem()os.freemem() 查看 操作系统 的内存情况

  • 堆外内存:那些不是通过 V8 分配的内存 - Buffer 对象

如需转载,请注明出处